
/* Generated by Interface Builder */

#import "LyapunovView.h"
#import <math.h>
#import <appkit/tiff.h>
#import <appkit/color.h>
#import <appkit/graphics.h>
#import <appkit/Control.h>
#import <appkit/Matrix.h>
#import <appkit/NXColorWell.h>
#import <dpsclient/psops.h>
#import <appkit/Application.h>
#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>


@implementation LyapunovView

- initFrame:(const NXRect *)frm		// designated initializer for a view
{
    [super initFrame:frm];
    return self;
}

- appDidInit:sender				// final initialization
{
    // set up default colors
    fromColor =  NXConvertRGBToColor(1.0, 1.0, 0.0);
    toColor =  NXConvertRGBToColor(0.01, 0.01, 0.0);
    backColor = NXConvertRGBToColor(0.0, 0.0, 0.05);
    [fromShade setColor:fromColor];
    [toShade setColor:toColor];
    [backShade setColor:backColor];
    
    // set up plot type
    singleDot = NO;
    [plotType selectCellWithTag:singleDot];
    
    // set up default position
    xPos = 0.0; yPos = 0.0;
    [[posMatrix findCellWithTag:0] setFloatValue:xPos];
    [[posMatrix findCellWithTag:1] setFloatValue:yPos];
    
    // set up default scale
    xScale = 1.0; yScale = 1.0;
    [[scaleMatrix findCellWithTag:0] setFloatValue:xScale];
    [[scaleMatrix findCellWithTag:1] setFloatValue:yScale];
    
    // set up default initial value
    initial = 0.1;
    [[initialValue findCellWithTag:0] setFloatValue:initial];
    
    // set up default depth
    deep = 4000;
    [[depth findCellWithTag:0] setIntValue:deep];
    
    // set up default pattern
    [[pattern findCellWithTag:0] setStringValue:"ab"];
    
    // set up contrast
    [contrastText setFloatValue:2.0];
    [contrastSlider setFloatValue:2.0];
    
    return self;
}

- contrast:sender			// adjust image contrast coeff
{
    contrast = [sender floatValue];
    [contrastText setFloatValue:contrast];
    [contrastSlider setFloatValue:contrast];
    
    return self;
}

- go:sender				// begin calculation of image
{
    register float total, t, tNew, r, a, b, expSum;
    float spMul, rb, gb, bb, rd, gd, bd, rk, gk, bk;
    register int ndx, x, n, pix, y, avgCnt;
    NXRect pixel, line;
    float log2 = log(2);
    
    pixel.size.width = 1;
    pixel.size.height = 1;
    line.size.height = 1;
    line.size.width = XSIZE;
    line.origin.x = 0;
    
    [self newParam:self];	// be sure we're up to date
    
    // set up color constants
    rb = NXRedComponent(fromColor);
    gb = NXGreenComponent(fromColor);
    bb = NXBlueComponent(fromColor);
    rd = NXRedComponent(toColor) - rb;
    gd = NXGreenComponent(toColor) - gb;
    bd = NXBlueComponent(toColor) - bb;
    rk = NXRedComponent(backColor);
    gk = NXGreenComponent(backColor);
    bk = NXBlueComponent(backColor);
    
    // main calc loop: calc line & draw it.
    expSum = 0; avgCnt = 0;
    [self lockFocus];
    for (y=0; y<YSIZE; y++) {
        b = yPos + y*yScale/YSIZE; //  get physical y-coord
        for (x=0; x<XSIZE; x++) {
	    a = xPos + x*xScale/XSIZE; //  get physical x-coord
	    total = 0.0; t = initial; ndx = 0;
	    for (n=0; n<deep; n++) {
	        r = (patternNum[ndx] ? a : b);
	        tNew = t * (1 - t) * r;
		ndx++;
		if (ndx >= patternLength) ndx = 0;
		total += log(fabs(r - 2 * r * tNew)) / log2;
		t = tNew;
	    }
	    space[x][y] = total / deep; // Lyapunov exponent approx.
	    // put into pixel map w/color	    
	    pix = (x+(YSIZE-y-1)*XSIZE)*RGB;
	    if (total >= 0) {
	        pixels[pix] = 255 * rk;  // pix+RED = pix because RED=0
		pixels[pix+GREEN] = 255 * gk;
		pixels[pix+BLUE] = 255 * bk;
		space[x][y] = 0;
	    } else {  // no chaos, so color it
	        spMul = fabs(space[x][y]/contrast);  // scale into color map
		if (spMul > 1.0) spMul = 1.0;
		if (fabs(space[x][y]) < 100000.0) {
		    expSum += space[x][y];
		    avgCnt++;
		}
		pixels[pix] = 255 *(spMul * rd + rb);
	        pixels[pix+GREEN]= 255*(spMul * gd + gb);
	        pixels[pix+BLUE] = 255*(spMul * bd + bb);
	    }
	    if (singleDot) {
	        // plot a pixel of it
	        pixel.origin.x = x;
	        pixel.origin.y = y;
	        NXImageBitmap(&pixel, 1, 1, 8, 3, 
		    NX_MESHED, NX_COLORMASK,
		    &pixels[(x+(YSIZE-y-1)*XSIZE)*RGB],
		    NULL, NULL, NULL, NULL);
	        [window flushWindow];
	        NXPing();
	    }
	}
	if (!singleDot) {
	    // plot a line of it
	    line.origin.y = y;
	    NXImageBitmap(&line, XSIZE, 1, 8, 3,
	        NX_MESHED, NX_COLORMASK, &pixels[(YSIZE-y-1)*XSIZE*RGB],
		NULL, NULL, NULL, NULL);
	    [window flushWindow];
	    NXPing();
	}
    }
    [self unlockFocus];
    [avgOut setFloatValue:(expSum/avgCnt)];
    [reApplyButton setEnabled:YES];
    return self;
}

- reApply:sender		// re do coloring on plot
{
    register int x, y, pix;
    float rb, gb, bb, rd, gd, bd, rk, gk, bk;
    float spMul;
    NXRect pixel, line;
    
    [self newParam:self];	// be sure we're up to date
    // set up plotting bounds
    pixel.size.width = 1; pixel.size.height = 1;
    line.size.height = 1; line.size.width = XSIZE;
    line.origin.x = 0;
    // get color params
    rb = NXRedComponent(fromColor); gb = NXGreenComponent(fromColor);
    bb = NXBlueComponent(fromColor); rd = NXRedComponent(toColor) - rb;
    gd = NXGreenComponent(toColor) - gb; bd = NXBlueComponent(toColor) - bb;
    rk = NXRedComponent(backColor); gk = NXGreenComponent(backColor);
    bk = NXBlueComponent(backColor);
    // re-apply colors
    [self lockFocus];
    for (y=0; y<YSIZE; y++) {
        for (x=0; x<XSIZE; x++) {
	    pix = (x+(YSIZE-y-1)*XSIZE)*RGB;
	    if (space[x][y] >= 0) {
	        pixels[pix] = 255 * rk;  // pix+RED = pix because RED=0
		pixels[pix+GREEN] = 255 * gk;
		pixels[pix+BLUE] = 255 * bk;
	    } else {  // no chaos, so color it
	        spMul = fabs(space[x][y]/contrast);  // scale into color map
		if (spMul > 1.0) spMul = 1.0;
		pixels[pix] = 255 *(spMul * rd + rb);
	        pixels[pix+GREEN]= 255*(spMul * gd + gb);
	        pixels[pix+BLUE] = 255*(spMul * bd + bb);
	    }
	}
	// plot a line of it
	line.origin.y = y;
	NXImageBitmap(&line, XSIZE, 1, 8, 3,
	    NX_MESHED, NX_COLORMASK, &pixels[(YSIZE-y-1)*XSIZE*RGB],
	    NULL, NULL, NULL, NULL);
	[window flushWindow];
	NXPing();
    } 
    [self unlockFocus];   
    return self;
}

- newParam:sender			// set up new parameters for calc
{
    int count;
    
    // get colors
    fromColor = [fromShade color];
    toColor = [toShade color];
    backColor = [backShade color];
    
    // get plotting method (dots or lines)
    singleDot = [[plotType selectedCell] tag];
    
    // get x-y coordinates
    xPos = [[posMatrix findCellWithTag:0] floatValue];
    yPos = [[posMatrix findCellWithTag:1] floatValue];
    
    // get x-y scaling
    xScale = [[scaleMatrix findCellWithTag:0] floatValue];
    yScale = [[scaleMatrix findCellWithTag:1] floatValue];
    
    // get initial value for each point
    initial = [[initialValue findCellWithTag:0] floatValue];
    
    // get depth
    deep = [[depth findCellWithTag:0] intValue];
    
    // get pattern
    patternString = [[pattern findCellWithTag:0] stringValue];
    for(count=0; patternString[count]!='\0'; count++) {
        patternNum[count] = ((patternString[count]=='a') ? 0 : 1);
    }
    patternLength = count;
    return self;
}

- drawSelf:(NXRect *)rects :(int)rectCount	// redraws the screen.
{
    PSsetgray(NX_DKGRAY);
    NXRectFill(&bounds);
    return self;
}

- mouseDown:(NXEvent *)e	// stolen from MandelView.m
/*
 * We implement the mouseDown method so the user can sweep out a section of
 * the view and select that as the current x,y,scale coordinates.
 */
{
  int looping = YES;
  int oldMask;
  NXRect bbox;
  NXPoint startPoint, currPoint;
  double newX, newY, newDX, newDY;
  NXCoord aspectRatio;
  float mvX = xPos;
  float mvY = yPos;
  float mvDX = xScale;
  float mvDY = yScale;
  
  aspectRatio = bounds.size.height / bounds.size.width;

  oldMask =  [window addToEventMask:NX_MOUSEDRAGGEDMASK];
  startPoint = e->location;
  [self convertPoint:&startPoint fromView:nil];
  NXSetRect(&bbox,startPoint.x,startPoint.y,0.0,0.0);
  [self lockFocus];
  while (looping) {
    e=[NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK];
    currPoint = e->location;
    [self convertPoint:&currPoint fromView:nil];
    bbox.size.width = 2*(currPoint.x - startPoint.x);
    bbox.size.height = 2*(currPoint.y - startPoint.y);
    /* Normalize bbox to always have positive width and height */
    if (bbox.size.width < 0) bbox.size.width = -bbox.size.width;
    if (bbox.size.height < 0) bbox.size.height = -bbox.size.height;
    /*
     * constrain the box to have the aspect ratio of the view.  Choose
     * whichever dimension is closer to the desired result.
     */
    if ((bbox.size.height/bbox.size.width) > aspectRatio) {
      bbox.size.height = bbox.size.width * aspectRatio;
    } else {
      bbox.size.width = bbox.size.height / aspectRatio;
    }
    /* The startPoint is always at the center of the bbox */
    bbox.origin.x = startPoint.x - .5 * bbox.size.width;
    bbox.origin.y = startPoint.y - .5 * bbox.size.height;
    PSnewinstance();
    if (looping = (e->type == NX_MOUSEDRAGGED)) {
      PSsetinstance(YES);
      PSsetgray(NX_WHITE);
      NXFrameRect(&bbox);
      PSsetinstance(NO);
    }
  }
  [self unlockFocus];
  [window setEventMask:oldMask];
  if ((bbox.size.width > 0) && (bbox.size.height > 0)) {
    /*
     * At this point, bbox is in window coordinates.  Set new controller
     * parameters based on this view's coordinates and the bounding box.
     */
    newDX = (bbox.size.width*mvDX)/bounds.size.width;
    newDY = (bbox.size.height*mvDY)/bounds.size.height;
    newX = ((startPoint.x + bounds.origin.x) / bounds.size.width);
    newX = newX * mvDX + mvX - newDX/2;
    newY = ((startPoint.y + bounds.origin.y) / bounds.size.height);
    newY = newY * mvDY + mvY - newDY/2;

    /*
     * Note that we only update the text fields -- we
     * don't update the internal instance variables.  If we were to update
     * the internal ivs, then the user would only get one chance with the
     * mouse down method because subsequent mouse-downs would work in terms
     * of the new coordinate system.
     */
    [[posMatrix findCellWithTag:0] setFloatValue:newX];
    [[posMatrix findCellWithTag:1] setFloatValue:newY];
    [[scaleMatrix findCellWithTag:0] setFloatValue:newDX];
    [[scaleMatrix findCellWithTag:1] setFloatValue:newDY];
  }
  return self;
}

@end
